package com.springboot.middle.give_the_thumbsup.server.service;

import com.springboot.middle.give_the_thumbsup.model.entity.Praise;
import com.springboot.middle.give_the_thumbsup.model.mapper.PraiseMapper;
import com.springboot.middle.give_the_thumbsup.server.dto.PraiseDto;
import com.springboot.middle.give_the_thumbsup.server.dto.PraiseRankDto;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collection;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@Service
public class PraiseServiceImpl implements PraiseService{
    private static final Logger log = LoggerFactory.getLogger(PraiseServiceImpl.class);
    //定义点赞时分布式锁的对应key
    private static final String keyAddBlogLock = "RedisBlogPraiseAddLock";

    @Autowired
    private PraiseMapper praiseMapper;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RedisPraise redisPraise;

    /**
     * 点赞博客-无分布式锁
     *
     * @param dto blogId, userId
     * @throws Exception exception
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addPraise(PraiseDto dto) throws Exception {
        Praise praise = praiseMapper.selectByBlogUserId(dto.getBlogId(), dto.getUserId());
        if (praise == null) {
            //如果没有点赞记录, 则创建点赞实体信息
            Praise p = new Praise();
            BeanUtils.copyProperties(dto, p);
            Date date = new Date();
            p.setPraiseTime(date);
            p.setStatus(1);
            p.setCreateTime(date);
            p.setUpdateTime(date);
            //插入点赞记录
            int total = praiseMapper.insertSelective(p);
            if(total > 0) {
                //如果插入成功, 则输出打印相应的信息, 并将用户点赞记录添加至缓存中
                log.info("--点赞博客,-{}-无锁-插入点赞记录成功---",dto.getBlogId());
                redisPraise.cachePraiseBlog(dto.getBlogId(), dto.getUserId(), 1);
                this.cachePraiseTotal();
            }
        }
    }

    /**
     * 点赞博客-分布式锁
     *
     * @param dto blogId, userId
     * @throws Exception exception
     */
    @Override
    public void addPraiseLock(PraiseDto dto) throws Exception {
        //定义用于获取分布式锁的Redis的key
        final String lockName = keyAddBlogLock + dto.getBlogId() + "-" + dto.getUserId();
        //获取一次性锁对象
        RLock lock = redissonClient.getLock(lockName);
        //上锁并在10秒钟自动释放,可用于避免Redis节点宕机时出现死锁
        lock.lock(10L, TimeUnit.SECONDS);

        try {
            Praise praise = praiseMapper.selectByBlogUserId(dto.getBlogId(), dto.getUserId());
            if (praise == null) {
                //如果没有点赞记录, 则创建点赞实体信息
                Praise p = new Praise();
                BeanUtils.copyProperties(dto, p);
                Date date = new Date();
                p.setPraiseTime(date);
                p.setStatus(1);
                p.setCreateTime(date);
                p.setUpdateTime(date);
                //插入点赞记录
                int total = praiseMapper.insertSelective(p);
                if(total > 0) {
                    //如果插入成功, 则输出打印相应的信息, 并将用户点赞记录添加至缓存中
                    log.info("--点赞博客,-{}-加分布式锁-插入点赞记录成功---",dto.getBlogId());
                    redisPraise.cachePraiseBlog(dto.getBlogId(), dto.getUserId(), 1);
                    this.cachePraiseTotal();
                }
            }
        } catch (Exception e) {
            log.error("--点赞博客,-{}-分布式锁-发生未知异常--",dto.getBlogId());
            throw e;
        } finally {
            if (lock.isLocked()) {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
    }

    /**
     * 取消点赞博客
     *
     * @param dto blogId, userId
     * @throws Exception exception
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelPraise(PraiseDto dto) throws Exception {
        //判断当前参数的合法性
        if (dto.getBlogId() != null && dto.getUserId() != null) {
            //当前用户取消点赞博客 - 更新相应的记录信息
            int result = praiseMapper.cancelPraiseBlog(dto.getBlogId(), dto.getUserId());
            //判断是否更新成功
            if (result > 0) {
                log.info("取消点赞博客-{}-更新点赞记录成功---", dto.getBlogId());
                redisPraise.cachePraiseBlog(dto.getBlogId(), dto.getUserId(), 0);
                this.cachePraiseTotal();
            }
        }
    }

    /**
     * 获取博客的点赞数
     *
     * @param blogId 博客id
     * @return 对应的点赞数
     * @throws Exception exception
     */
    @Override
    public Long getBlogPraiseTotal(Integer blogId) throws Exception {
        return redisPraise.getCacheTotalBlog(blogId);
    }

    /**
     * 获取博客点赞总数排行榜 - 不用缓存
     *
     * @return 排行榜
     * @throws Exception exception
     */
    @Override
    public Collection<PraiseRankDto> getRankNoRedisson() throws Exception {
        return praiseMapper.getPraiseRank();
    }

    /**
     * 获取博客点赞总数排行榜 - 用缓存
     *
     * @return 排行榜
     * @throws Exception exception
     */
    @Override
    public Collection<PraiseRankDto> getRankWithRedisson() throws Exception {
        return redisPraise.getBlogPraiseRank();
    }

    /**
     * 将当前博客id对应的点赞总数构造为实体, 并添加进RList中 - 构造排行榜
     * 记录当前博客id - 点赞总数 - 实体排行榜
     */
    private void cachePraiseTotal() {
        try {
            redisPraise.rankBlogPraise();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
